1  /**
2  * Managed memory is a safe wrapper around existing heap allocated memory.
3  * Safe heap allocated memory ensures that it cannot be deallocated until such time
4  *  it is no longer used.
5  * 
6  * This has similar usages with refcounting and duplication.
7  *
8  * License:
9  *              Copyright Devisualization (Richard Andrew Cattermole) 2014 - 2017.
10  *     Distributed under the Boost Software License, Version 1.0.
11  *        (See accompanying file LICENSE_1_0.txt or copy at
12  *              http://www.boost.org/LICENSE_1_0.txt)
13  */
14 module devisualization.util.core.memory.managed;
15 import stdx.allocator : ISharedAllocator, IAllocator, make, dispose, processAllocator, theAllocator;
16 import std.typecons : Tuple;
17 import std.traits : CopyConstness, isBasicType, isPointer, ForeachType, isArray, isDynamicArray, Unqual;
18 
19 version(Windows) {
20 	import core.sys.windows.com : IUnknown;
21 }
22 
23 /**
24  * Defaults to using ReferenceCountedManager as memory manager
25  * 
26  * See_Also:
27  *      ReferenceCountedManager
28  */
29 auto managers() {
30 	MemoryManagerS!(Tuple!(ReferenceCountedManager)) ret;
31 	return ret;
32 }
33 
34 /**
35  * Puts together a group of managers for use with managed!T
36  * 
37  * See_Also:
38  *      managed
39  */
40 auto managers(U...)(U args) if (U.length > 0) {
41 	return MemoryManagerS!(Tuple!U)(Tuple!U(args));
42 }
43 
44 /**
45  * Implements a managed memory model representation for heap allocated data.
46  * 
47  * Ensures all memory allocated is accessed in a safe manner but may result in unsafe data usage. $(BR)
48  * Original concept was for language support $(WEB http://cattermole.co.nz/article/managed_memory, link).
49  * 
50  * This can be thought of in terms of c++ const which is a head only const. A head const is where the pointer may not
51  *  not change but the data is modifiable. For example an element in array may be changed but it may not be changed to
52  *  another array.
53  * 
54  * Compatibility with allocators are a requirement, using IAllocator to represent it. This is a optional element and
55  *  will by default use theAllocator(). This allows fine grain control over how it allocates on the $(B heap).
56  * 
57  * When using a COM object as the source, it will assume the responsiblity of AddRef and Releaseing instead
58  *  of opInc + opDec. Ensuring deallocation when all references are removed. Rule: has secondary ownership and
59  *  will not attempt to deallocate. But may deallocate at any time.
60  * 
61  * A managed type is compatible with $(D @safe), "$(D @nogc)" and $(D nothrow). However only on the type being represented
62  *  all of the methods upon $(D managed!T) itself is @trusted because of how allocators work.
63  * 
64  * You may not cast away $(D managed!T) or get the real instance of the type. For classes you may cast to a more generic
65  * interface/class but not to a more specialised one. For basic types it may be done to another if its size is the same.
66  * If the usage will not escape it may be freely used as the real type without casting.
67  * 
68  * When the managed type is an array, it will wrap all slice actions into returning a $(D managed!T) value.
69  * This means that all slices are always safe to use and will keep the entirety of the array around until dealloation.
70  * Otherwise in normal operations it will act as if it was a normal D array
71  *  only using allocators for expanding and shrinking.
72  * 
73  * To retrieve a null version, just use $(D (managed!T).init). It will directly compare to $(D null) as true.
74  *  Since internally it will be comparing to a pointer while using $(D is).
75  * 
76  * See_Also:
77  *      IMemoryManager, ISharedMemoryManager, managers, ReferenceCountedManager, NeverDeallocateManager
78  */
79 struct managed(Type) {
80 	static assert(!is(Type==union), "managed!T does not support unions as head type");
81 	static assert(!isPointer!Type, "managed!T does not support pointers");
82 	
83 	static if (ManagedIsShared) {
84 		alias AllocatorType = shared(ISharedAllocator);
85 		alias __managedAllocator = processAllocator;
86 	} else {
87 		alias AllocatorType = IAllocator;
88 		alias __managedAllocator = theAllocator;
89 	}
90 	
91 	/// Has this instance been initialized?
92 	bool isNull() {
93 		return __managedInternal.managers is null;
94 	}
95 	
96 	// \/ Construction \/
97 	
98 	static {
99 		/// Constructs managed!T where T is a class
100 		managed!Type opCall(MemoryManagers)(Type value, MemoryManagers managers, AllocatorType allocator=__managedAllocator)
101 			if (is(Type==class) || is(Type==interface))
102 			in {
103 				assert(value !is null, "Class instance must not be null.");
104 				assert(allocator !is null, "Allocator instance must not be null");
105 			} body {
106 			managed!Type ret;
107 			
108 			ManagedInternalData.create(ret, managers, allocator);
109 			ret.__managedInternal.value = value;
110 			
111 			return ret;
112 		}
113 		
114 		/// Constructs managed!T where T is a struct or basic type
115 		managed!Type opCall(MemoryManagers)(Type value, MemoryManagers managers, AllocatorType allocator=__managedAllocator)
116 			if (isBasicType!Type || is(Type == struct))
117 			in {
118 				assert(allocator !is null, "Allocator instance must not be null");
119 			} body {
120 			managed!Type ret;
121 			
122 			ManagedInternalData.create(ret, managers, allocator);
123 			ret.__managedInternal.value = value;
124 			
125 			return ret;
126 		}
127 		
128 		/// Constructs managed!T where T is an array with elements of class/struct/basic types
129 		managed!Type opCall(MemoryManagers)(Type value, MemoryManagers managers, AllocatorType allocator=__managedAllocator)
130 			if (isArray!Type)
131 			in {
132 				assert(value.length > 0, "Value instance must not be empty.");
133 				assert(allocator !is null, "Allocator instance must not be null");
134 			} body {
135 			managed!Type ret;
136 			
137 			ManagedInternalData.create(ret, managers, allocator);
138 			ret.__managedInternal.value = value;
139 			
140 			return ret;
141 		}
142 		
143 		/// Constructs managed!T where T is a pointer to a class/struct/array/basic type
144 		managed!Type opCall(MemoryManagers)(Type* value, MemoryManagers managers, AllocatorType allocator=__managedAllocator)
145 		in {
146 			assert(value !is null, "Value instance must not be null.");
147 			assert(allocator !is null, "Allocator instance must not be null");
148 		} body {
149 			managed!Type ret;
150 			
151 			ManagedInternalData.create(ret, managers, allocator);
152 			ret.__managedInternal.selfReUpdate = value;
153 			
154 			return ret;
155 		}
156 		
157 		/// Constructs managed!T where T is a struct or class type with arguments
158 		managed!Type opCall(MemoryManagers, ArgsT...)(MemoryManagers managers, Tuple!ArgsT args, AllocatorType allocator=__managedAllocator)
159 			if (is(Type == class) || is(Type == struct))
160 			in {
161 				assert(allocator !is null, "Allocator instance must not be null");
162 			} body {
163 			managed!Type ret;
164 			
165 			ManagedInternalData.create(ret, managers, allocator);
166 			
167 			static if (is(Type == class)) {
168 				ret.__managedInternal.value = allocator.make!Type(args.expand);
169 			} else static if (is(Type == struct)) {
170 				ret.__managedInternal.value = Type(args.expand);
171 			}
172 			
173 			return ret;
174 		}
175 	}
176 	
177 	// /\ Construction /\
178 	// \/ Type conversions \/
179 	
180 	static if (!(is(Type == const) || is(Type == immutable))) {
181 		/// Adds const
182 		managed!(const(Type)) opCast(Type2)() if (is(Type2==managed!(const(Type)))) {
183 			managed!(immutable(Type)) ret;
184 			
185 			__managedInternal.copyInto(ret);
186 			
187 			return ret;
188 		}
189 		
190 		/// Adds immutable
191 		managed!(immutable(Type)) opCast(Type2)() if (is(Type2==managed!(immutable(Type)))) {
192 			managed!(immutable(Type)) ret;
193 			
194 			__managedInternal.copyInto(ret);
195 			
196 			return ret;
197 		}
198 	}
199 	
200 	static if (is(Type==class) || is(Type==interface)) {
201 		/// Casts class to a more generic type
202 		managed!(CopyConstness!(Type, Type2)) opCast(Type2:managed!Type2)() if (is(Type:Type2)) {
203 			managed!(CopyConstness!(Type, Type2)) ret;
204 			
205 			__managedInternal.copyInto(ret);
206 			
207 			if (__managedInternal.selfReUpdate !is null) {
208 				ret.__managedInternal.selfReUpdate = cast(Type2*)__managedInternal.selfReUpdate;
209 			} else {
210 				ret.__managedInternal.value = cast(Type2)__managedInternal.value;
211 			}
212 			
213 			return ret;
214 		}
215 		
216 		/// Unsafe-ish dynamic cast, check if is null!
217 		managed!(CopyConstness!(Type, Type2)) opCast(Type2:managed!Type2)() if (!is(Type:Type2) && (is(Type2 == interface) || is(Type2 == class))) {
218 			managed!(CopyConstness!(Type, Type2)) ret;
219 			
220 			if (CopyConstness!(Type, Type2) v = cast(CopyConstness!(Type, Type2))__managedGet()) {
221 				__managedInternal.copyInto(ret);
222 				
223 				if (__managedInternal.selfReUpdate !is null) {
224 					ret.__managedInternal.selfReUpdate = cast(Type2*)__managedInternal.selfReUpdate;
225 				} else {
226 					ret.__managedInternal.value = cast(Type2)__managedInternal.value;
227 				}
228 			}
229 			
230 			return ret;
231 		}
232 	} else static if (is(Type:TypeIf_Class[], TypeIf_Class) && (is(TypeIf_Class == class) || is(TypeIf_Class==interface))) {
233 		/// Casts an array of classes to a more generic class type
234 		managed!(CopyConstness!(Type, Type2)[]) opCast(Type2:managed!(Type2[]))() if (is(Type:Type2)) {
235 			managed!(CopyConstness!(Type, Type2)[]) ret;
236 			
237 			__managedInternal.copyInto(ret);
238 			
239 			return ret;
240 		}
241 	} else static if (isBasicType!Type || is(Type == struct)) {
242 		/// Casts a basic type to a similar (aka same sized) type
243 		managed!(CopyConstness!(Type, Type2)) opCast(Type2:managed!Type2)()
244 		if ((isBasicType!Type2 || is(Type2 == struct)) && Type2.sizeof == Type.sizeof) {
245 			managed!(CopyConstness!(Type, Type2)) ret;
246 			
247 			__managedInternal.copyInto(ret);
248 			
249 			return ret;
250 		}
251 	} else static if (is(Type:TypeIf_StructBasic[], TypeIf_StructBasic) && (isBasicType!TypeIf_StructBasic || is(TypeIf_StructBasic == struct))) {
252 		/// Casts an array of structs or basic types to a similar (aka same sized) type
253 		managed!(CopyConstness!(TypeIf_StructBasic, Type2)[]) opCast(Type2:managed!(Type2[]))()
254 		if ((isBasicType!Type2 || is(Type2 == struct)) && Type2.sizeof == TypeIf_StructBasic.sizeof) {
255 			managed!(CopyConstness!(TypeIf_StructBasic, Type2)[]) ret;
256 			
257 			__managedInternal.copyInto(ret);
258 			
259 			return ret;
260 		}
261 	}
262 	
263 	// /\ Type convesions /\
264 	// \/ Reference counting stuff \/
265 	
266 	this(this) {
267 		if (__managedInternal.managers is null)
268 			return;
269 		
270 		__managedInternal.managers.onIncrement();
271 		
272 		static if (__traits(compiles, {__managedInternal.value.onIncrement();})) {
273 			__managedInternal.value.onIncrement();
274 		}
275 		
276 		version(Windows) {
277 			static if (is(MyType == class) && is(MyType : IUnknown)) {
278 				__managedInternal.value.AddRef();
279 			}
280 		}
281 	}
282 	
283 	~this() {
284 		if (__managedInternal.managers is null)
285 			return;
286 		
287 		__managedInternal.managers.onDecrement();
288 		
289 		static if (__traits(compiles, {__managedInternal.value.onDecrement();})) {
290 			__managedInternal.value.onDecrement();
291 		}
292 		
293 		bool beenReleased;
294 		version(Windows) {
295 			static if ((is(Type==class) || is(Type==interface)) && is(Type:IUnknown)) {
296 				beenReleased = __managedInternal.value.Release() == 0;
297 			}
298 		}
299 		
300 		if (beenReleased) {
301 			__managedInternal.allocator.dispose(__managedInternal.managers);
302 		} else if (__managedInternal.managers.shouldDeallocate) {
303 			if (__managedInternal.selfReUpdate is null && !__managedInternal.managers.typeShouldNeverDeallocate) {
304 				static if (is(Type==struct) || isBasicType!Type) {
305 				} else static if (isDynamicArray!Type) {
306 					__managedInternal.allocator.dispose(cast(Unqual!(ForeachType!Type)[])__managedInternal.value);
307 				} else static if (is(Type == class) || is(Type == interface)) {
308 					__managedInternal.allocator.dispose(cast(Unqual!Type)__managedInternal.value);
309 				}
310 			}
311 			__managedInternal.allocator.dispose(__managedInternal.managers);
312 		} else {
313 			// do nothing
314 			// the default behaviour from shouldDeallocate if it does not on call is true
315 		}
316 	}
317 	
318 	// /\ Reference counting stuff /\
319 	// \/ Getters \/
320 	
321 	static if (isArray!Type) {
322 		
323 	}
324 	
325 	alias __managedGet this;
326 	scope ref Type __managedGet() {
327 		if (__managedInternal.selfReUpdate is null)
328 			return __managedInternal.value;
329 		else return *__managedInternal.selfReUpdate;
330 	}
331 	
332 	// /\ Getters /\
333 	// \/ Internal stuff \/
334 	
335 	private {
336 		enum ManagedIsShared = is(Type == shared);
337 		ManagedInternalData __managedInternal;
338 		
339 		struct ManagedInternalData {
340 			Type value;
341 			Type* selfReUpdate;
342 			
343 			static if (ManagedIsShared) {
344 				shared(ISharedAllocator) allocator;
345 				shared(ISharedMemoryManager) managers;
346 				
347 				static void create(MemoryManagerST)(ref managed!Type ctx, MemoryManagerST managers, shared(ISharedAllocator) allocator) {
348 					ctx.__managedInternal.allocator = allocator;
349 					
350 					static if (is(MemoryManagerST : IMemoryManager)) {
351 						ctx.__managedInternal.managers = managers.dup(alloc);
352 					} else {
353 						ctx.__managedInternal.managers = allocator.make!(shared(MemoryManager!(Type, MemoryManagerST)))
354 							(allocator, managers);
355 					}
356 					
357 					ctx.__managedInternal.managers.onIncrement();
358 				}
359 			} else {
360 				IAllocator allocator;
361 				IMemoryManager managers;
362 				
363 				static void create(MemoryManagerST)(ref managed!Type ctx, MemoryManagerST managers, IAllocator allocator) {
364 					ctx.__managedInternal.allocator = allocator;
365 					
366 					static if (is(MemoryManagerST : IMemoryManager)) {
367 						ctx.__managedInternal.managers = managers.dup(alloc);
368 					} else {
369 						ctx.__managedInternal.managers = allocator.make!(MemoryManager!(Type, MemoryManagerST))
370 							(allocator, managers);
371 					}
372 					
373 					ctx.__managedInternal.managers.onIncrement();
374 				}
375 			}
376 			
377 			void copyInto(Into)(ref managed!Into destination) {
378 				destination.__managedInternal.value = cast(Into)value;
379 				destination.__managedInternal.selfReUpdate = cast(Into*)selfReUpdate;
380 				destination.__managedInternal.allocator = allocator;
381 				destination.__managedInternal.managers = managers;
382 				destination.__managedInternal.managers.onIncrement();
383 			}
384 		}
385 	}
386 	
387 	// /\ Internal stuff /\
388 }
389 
390 ///
391 interface IMemoryManager {
392 	IMemoryManager dup(IAllocator alloc);
393 	
394 	/**
395 	 * on ~this() call
396 	 */
397 	bool shouldDeallocate() @safe nothrow;
398 	
399 	/**
400 	 * Is the type even allowed to be deallocated?
401 	 */
402 	bool typeShouldNeverDeallocate() @safe nothrow;
403 	
404 	/**
405 	 * this()
406 	 */
407 	void onIncrement() @safe nothrow;
408 	/**
409 	 * ~this()
410 	 */
411 	void onDecrement() @safe nothrow;
412 }
413 
414 ///
415 interface ISharedMemoryManager {
416 	shared(ISharedMemoryManager) dup(shared(ISharedAllocator) alloc) shared;
417 	
418 	
419 	/**
420 	 * Is the type even allowed to be deallocated?
421 	 */
422 	bool typeShouldNeverDeallocate() @safe nothrow shared;
423 	
424 	/**
425 	 * on ~this() call
426 	 */
427 	bool shouldDeallocate() @safe nothrow shared;
428 	
429 	/**
430 	 * this()
431 	 */
432 	void onIncrement() @safe nothrow shared;
433 	/**
434 	 * ~this()
435 	 */
436 	void onDecrement() @safe nothrow shared;
437 }
438 
439 ///
440 struct ReferenceCountedManager {
441 	private import core.atomic : atomicOp;
442 	shared(int) counter;
443 	
444 	@safe nothrow {
445 		bool shouldDeallocate() { return counter <= 0; }
446 		bool shouldDeallocate() shared { return counter <= 0; }
447 		
448 		void onIncrement() { atomicOp!"+="(counter, 1); }
449 		void onIncrement() shared { atomicOp!"+="(counter, 1); }
450 		
451 		void onDecrement() { atomicOp!"-="(counter, 1); }
452 		void onDecrement() shared { atomicOp!"-="(counter, 1); }
453 	}
454 }
455 
456 ///
457 struct NeverDeallocateManager {
458 	bool typeShouldNeverDeallocate() @safe nothrow {
459 		return true;
460 	}
461 	
462 	bool typeShouldNeverDeallocate() @safe nothrow shared {
463 		return true;
464 	}
465 }
466 
467 private {
468 	struct MemoryManagerS(Type) {
469 		Type managers;
470 		alias managers this;
471 		
472 		this(Type v) {
473 			managers = v;
474 		}
475 	}
476 	
477 	final class MemoryManager(UserType, UserManagers) : IMemoryManager if (!is(UserType == shared) &&
478 		isValidUserManagers!(UserType, UserManagers)) {
479 		
480 		UserManagers managers, originalManagers;
481 		bool[UserManagers.length] doDeAllocateMemMgrs;
482 		IAllocator allocator;
483 		
484 		this(IAllocator allocator, UserManagers managers) {
485 			this.allocator = allocator;
486 			this.managers = managers;
487 			this.originalManagers = managers;
488 			
489 			foreach(i, ref manager; managers) {
490 				static if (is(typeof(manager) == class)) {
491 					if (manager is null) {
492 						manager = allocator.make!(typeof(manager));
493 						doDeAllocateMemMgrs[i] = true;
494 					}
495 				}
496 			}
497 		}
498 		
499 		bool shouldDeallocate() @safe nothrow {
500 			bool hadOne;
501 			
502 			static if (UserManagers.length >= 1) {
503 				foreach(ref manager; managers) {
504 					static if (__traits(compiles, {bool ret = manager.shouldDeallocate();})) {
505 						bool ret = manager.shouldDeallocate();
506 						hadOne = true;
507 						
508 						if (ret)
509 							return ret;
510 					}
511 				}
512 			}
513 			
514 			return !hadOne;
515 		}
516 		
517 		bool typeShouldNeverDeallocate() @safe nothrow {
518 			bool hadOne;
519 			
520 			static if (UserManagers.length >= 1) {
521 				foreach(ref manager; managers) {
522 					static if (__traits(compiles, {bool ret = manager.typeShouldNeverDeallocate();})) {
523 						bool ret = manager.typeShouldNeverDeallocate();
524 						hadOne = true;
525 						
526 						if (!ret)
527 							return ret;
528 					}
529 				}
530 			}
531 			
532 			return hadOne;
533 		}
534 		
535 		void onIncrement() @safe nothrow {
536 			foreach(ref manager; managers) {
537 				static if (__traits(compiles, {manager.onIncrement();})) {
538 					manager.onIncrement();
539 				}
540 			}
541 		}
542 		
543 		void onDecrement() @safe nothrow {
544 			foreach(ref manager; managers) {
545 				static if (__traits(compiles, {manager.onDecrement();})) {
546 					manager.onDecrement();
547 				}
548 			}
549 		}
550 		
551 		IMemoryManager dup(IAllocator alloc) {
552 			return alloc.make!(MemoryManager!(UserType, UserManagers))(alloc, originalManagers);
553 		}
554 		
555 		~this() {
556 			foreach(i, ref manager; managers) {
557 				static if (is(typeof(manager) == class)) {
558 					if (doDeAllocateMemMgrs[i]) {
559 						allocator.dispose(manager);
560 					}
561 				}
562 			}
563 		}
564 	}
565 	
566 	final class MemoryManager(UserType, UserManagers) : ISharedMemoryManager if (is(UserType == shared) &&
567 		isValidUserManagers!(UserType, UserManagers)) {
568 		
569 		UserManagers managers, originalManagers;
570 		bool[UserManagers.length] doDeAllocateMemMgrs;
571 		shared(ISharedAllocator) allocator;
572 		
573 		this(shared(ISharedAllocator) allocator, UserManagers managers) shared {
574 			this.allocator = allocator;
575 			this.managers = managers;
576 			this.originalManagers = managers;
577 			
578 			foreach(i, ref manager; managers) {
579 				static if (is(typeof(manager) == class)) {
580 					if (manager is null) {
581 						manager = allocator.make!(typeof(manager));
582 						doDeAllocateMemMgrs[i] = true;
583 					}
584 				}
585 			}
586 		}
587 		
588 		bool shouldDeallocate() @safe nothrow shared {
589 			bool hadOne;
590 			
591 			static if (UserManagers.length >= 1) {
592 				foreach(ref manager; managers) {
593 					static if (__traits(compiles, {bool ret = manager.shouldDeallocate();})) {
594 						bool ret = manager.shouldDeallocate();
595 						hadOne = true;
596 						
597 						if (!ret)
598 							return ret;
599 					}
600 				}
601 			}
602 			
603 			return !hadOne;
604 		}
605 		
606 		
607 		bool typeShouldNeverDeallocate() @safe nothrow shared {
608 			bool hadOne;
609 			
610 			static if (UserManagers.length >= 1) {
611 				foreach(ref manager; managers) {
612 					static if (__traits(compiles, {bool ret = manager.typeShouldNeverDeallocate();})) {
613 						bool ret = manager.typeShouldNeverDeallocate();
614 						hadOne = true;
615 						
616 						if (ret)
617 							return ret;
618 					}
619 				}
620 			}
621 			
622 			return hadOne;
623 		}
624 		
625 		void onIncrement() @safe nothrow shared {
626 			foreach(ref manager; managers) {
627 				static if (__traits(compiles, {manager.onIncrement();})) {
628 					manager.onIncrement();
629 				}
630 			}
631 		}
632 		
633 		void onDecrement() @safe nothrow shared {
634 			foreach(ref manager; managers) {
635 				static if (__traits(compiles, {manager.onDecrement();})) {
636 					manager.onDecrement();
637 				}
638 			}
639 		}
640 		
641 		shared(ISharedMemoryManager) dup(shared(ISharedAllocator) alloc) shared {
642 			return alloc.make!(shared(MemoryManager!(UserType, UserManagers)))(alloc, originalManagers);
643 		}
644 		
645 		~this() shared {
646 			foreach(i, ref manager; managers) {
647 				static if (is(typeof(manager) == class)) {
648 					if (doDeAllocateMemMgrs[i]) {
649 						allocator.dispose(manager);
650 					}
651 				}
652 			}
653 		}
654 	}
655 	
656 	bool isValidUserManagers(UserType, Ts)() pure {
657 		bool ret = true;
658 		
659 		foreach(T; Ts.Types) {
660 			static if ((is(UserType == shared) == is(T == shared)) && !(is(T == class) || is(T == struct))) {
661 				ret = false;
662 			}
663 		}
664 		
665 		return ret;
666 	}
667 }